自定义指令:debounce & throttle
概述
防抖(debounce)和节流(throttle)是前端性能优化的两个核心概念。本节将它们封装为 Vue 自定义指令 v-debounce 和 v-throttle,限制函数执行频率,避免频繁触发导致的性能问题。
防抖与节流对比
防抖 (debounce):延迟执行,在等待时间内再次触发则重新计时
事件: ─┬──┬──────┬──────────────────────→
执行: ─────────────────────────────┬──────→ (最后一次触发后 500ms 执行)
节流 (throttle):固定间隔执行,不管触发多频繁
事件: ─┬──┬──────┬──┬──────┬──────────→
执行: ──┬──────────┬──────────┬────────→ (每 500ms 执行一次)
text
| 维度 | debounce (防抖) | throttle (节流) |
|---|---|---|
| 执行时机 | 停止触发后执行 | 固定间隔执行 |
| 适用场景 | 搜索输入、表单验证 | 滚动事件、窗口 resize |
| 触发频率 | 最后一次触发后执行 | 按固定频率执行 |
防抖指令 v-debounce
// directives/modules/debounce.ts
import type { Directive, DirectiveBinding } from 'vue'
/**
* v-debounce 指令
* 用法:<el-button v-debounce="handleClick">提交</el-button>
* 点击后等待 500ms 执行,期间重复点击则重新计时
*/
const debounce: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const handler = binding.value
const delay = binding.arg ? parseInt(binding.arg) : 500
if (typeof handler !== 'function') {
throw new Error('v-debounce binding value must be a function')
}
let timer: ReturnType<typeof setTimeout> | null = null
el._debounceClick = () => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
handler()
timer = null
}, delay)
}
el.addEventListener('click', el._debounceClick)
},
beforeUnmount(el: HTMLElement) {
if (el._debounceClick) {
el.removeEventListener('click', el._debounceClick)
}
}
}
export default debounce
typescript
节流指令 v-throttle
// directives/modules/throttle.ts
import type { Directive, DirectiveBinding } from 'vue'
/**
* v-throttle 指令
* 用法:<el-button v-throttle="handleClick">保存</el-button>
* 每隔 500ms 最多执行一次
*/
const throttle: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const handler = binding.value
const delay = binding.arg ? parseInt(binding.arg) : 500
if (typeof handler !== 'function') {
throw new Error('v-throttle binding value must be a function')
}
let timer: ReturnType<typeof setTimeout> | null = null
el._throttleClick = () => {
if (timer) return // 冷却中,跳过
timer = setTimeout(() => {
handler()
timer = null
}, delay)
}
el.addEventListener('click', el._throttleClick)
},
beforeUnmount(el: HTMLElement) {
if (el._throttleClick) {
el.removeEventListener('click', el._throttleClick)
}
}
}
export default throttle
typescript
使用方式
<template>
<!-- 防抖:输入框停止输入 500ms 后搜索 -->
<el-input
v-model="keyword"
placeholder="搜索..."
v-debounce:300="handleSearch"
/>
<!-- 防抖:按钮点击防重复提交 -->
<el-button v-debounce="handleSubmit" type="primary">
提交表单
</el-button>
<!-- 节流:滚动加载 -->
<div v-throttle:200="handleScroll" @scroll="onScroll">
长列表内容...
</div>
<!-- 节流:按钮点击限频 -->
<el-button v-throttle="handleSave">
保存
</el-button>
</template>
<script setup lang="ts">
function handleSearch() {
console.log('执行搜索')
}
function handleSubmit() {
console.log('提交表单')
}
function handleScroll() {
console.log('滚动事件处理')
}
function handleSave() {
console.log('保存数据')
}
</script>
vue
自定义延迟时间
通过 binding.arg 支持自定义延迟时间(单位 ms):
<!-- 默认 500ms -->
<el-button v-debounce="handler" />
<!-- 自定义 300ms -->
<el-button v-debounce:300="handler" />
<!-- 自定义 1000ms -->
<el-button v-throttle:1000="handler" />
html
应用场景
| 场景 | 指令 | 说明 |
|---|---|---|
| 搜索输入 | v-debounce:300 | 停止输入 300ms 后搜索 |
| 表单提交 | v-debounce | 防止重复提交 |
| 窗口 resize | v-throttle:200 | 每 200ms 最多处理一次 |
| 滚动加载 | v-throttle:300 | 每 300ms 最多触发一次加载 |
| 按钮点击 | v-throttle | 限制点击频率 |
实践要点
- 防抖适合搜索输入、表单提交等"停止操作后执行"的场景
- 节流适合滚动、resize 等需要"固定频率执行"的场景
beforeUnmount中必须清除定时器和事件监听,避免内存泄漏- 通过
binding.arg传递自定义延迟时间,默认 500ms - 绑定值必须为函数,否则抛出错误提示
↑